
// sys
#include <arpa/inet.h>
#include <string.h>
// 3rd-party
#include <event2/bufferevent.h>
#include <event2/util.h>
// mine
#include "connector.h"
#include "tcp_session.h"
#include "exception.h"

namespace {

void ConnEvt(bufferevent* bev, short what, void* ctx) {
  using namespace net_base;
  Connector::ConnCB* cb = (Connector::ConnCB*)ctx;
  int sock_ec = 0;
  TcpSessionPtr ptr;
  if (what & BEV_EVENT_CONNECTED) {
    ptr.reset(new TcpSession(bev));                    
  } else {
    evutil_socket_t fd = bufferevent_getfd(bev);
    (void)fd;
    sock_ec = evutil_socket_geterror(fd);
  }
  (*cb)(sock_ec, ptr);
  delete cb;

  if (!(what & BEV_EVENT_CONNECTED))
    bufferevent_free(bev);
}

}

namespace net_base {
void Connector::Connect(const char* ip, uint16_t port, const ConnCB& cb) {
  sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  if (1 != evutil_inet_pton(AF_INET, ip, &sin.sin_addr))
    throw sysapi_error(errno);
  sin.sin_family = AF_INET;
  sin.sin_port = htons(port);

  Connect((sockaddr*)&sin, sizeof(sin), cb);
}

void Connector::Connect(const char* ip_port, const ConnCB& cb) {
  sockaddr_in sin;
  int len = sizeof(sin);
  if (0 != evutil_parse_sockaddr_port(ip_port, (sockaddr*)&sin, &len))
    throw parse_ip_port_error(ip_port);

  Connect((sockaddr*)&sin, len, cb);
}

void Connector::Connect(sockaddr* sa, int socklen, const ConnCB& cb) {
  if (!cb) return;

  bufferevent* bev = bufferevent_socket_new(evbase_, -1, 
                                            BEV_OPT_CLOSE_ON_FREE|
                                            BEV_OPT_THREADSAFE|
                                            BEV_OPT_DEFER_CALLBACKS);
  if (!bev) throw create_socket_failed();
  bufferevent_setcb(bev, nullptr, nullptr, ConnEvt, new ConnCB(cb));
  if (0 != bufferevent_socket_connect(bev, sa, socklen))
    throw connect_failed();
}


} // namespace net_base

